package com.puzzlebazar.client.main.control;

import com.puzzlebazar.client.admin.AdminState;
import com.puzzlebazar.client.admin.SiteModel;
import com.puzzlebazar.client.main.model.AutoLink;
import com.puzzlebazar.client.main.model.PuzzleListInfo;
import com.puzzlebazar.client.main.model.PuzzleListInfoSimple;
import com.puzzlebazar.client.main.view.MainView;
import com.puzzlebazar.client.main.view.MainView.MainTokenFactory;
import com.puzzlebazar.client.main.control.EditPuzzleState.Type;
import com.puzzlebazar.client.puzzle.model.GenreFactory;
import com.puzzlebazar.client.puzzle.model.History;
import com.puzzlebazar.client.puzzle.model.HistoryImpl;
import com.puzzlebazar.client.puzzle.model.PuzzleFactory;
import com.puzzlebazar.client.puzzle.model.PuzzleModel;
import com.puzzlebazar.client.puzzleinfo.HistoryInfo;
import com.puzzlebazar.client.puzzleinfo.NewPuzzleInfo;
import com.puzzlebazar.client.puzzleinfo.PuzzleInfo;
import com.puzzlebazar.client.puzzleinfo.PuzzleInfoState;
import com.puzzlebazar.client.user.SettingsState;
import com.puzzlebazar.client.user.UserInfo;
import com.puzzlebazar.client.user.UserModel;
import com.puzzlebazar.client.util.Capabilities;
import com.puzzlebazar.client.util.ConnectionLock;
import com.puzzlebazar.client.util.TokenFactory;
import com.puzzlebazar.client.util.ConnectionLock.UnlockCallback;

public class MainController extends HistoryControllerImpl implements PuzzleTokenFactory {

	private final SiteModel settingsModel;
	private final UserModel     userModel;
	private final MainView      view;

	private final PuzzleListState   puzzleListState;
	private final NewPuzzleState    newPuzzleState;
	private final LoadPuzzleState   loadPuzzleState;
	private final PuzzleInfoState   puzzleInfoState;
	private final EditPuzzleState   editPuzzleState;
	private final SettingsState     settingsState;	
	private final AdminState        adminState;	
	private final WelcomeState      welcomeState;
	private final SearchState       searchState;
	
	private final ConnectionLock    loadSiteAndUserLock;
	
	/**
	 * Creates the main controller for the top-level state of the application 
	 */
	public MainController( 
			final SiteModel settingsModel, 
			final UserModel userModel, 
			final MainView view ) throws Exception {
		super();
		
		this.settingsModel = settingsModel;
		this.userModel = userModel;
		this.view = view;
		view.setTokenFactory( new MyMainTokenFactory() );
		
		puzzleListState = new PuzzleListState(userModel,view,this);
		newPuzzleState = new NewPuzzleState(userModel,view,this);
		loadPuzzleState = new LoadPuzzleState(userModel,view,this);
		puzzleInfoState = new PuzzleInfoState(userModel,view,this);
		editPuzzleState = new EditPuzzleState(userModel,view,this);
		settingsState = new SettingsState(userModel,view,this);
		adminState = new AdminState(settingsModel,userModel,view,this);
		welcomeState = new WelcomeState(settingsModel,userModel,view,this);
		searchState = new SearchState(userModel,view,this);

		
		loadSiteAndUserLock = new ConnectionLock( "Loading..." );
		loadSiteAndUserLock.setGlobalLockAndWhiteScreen(true);
		loadSiteAndUserLock.addUnlockCallback( new UnlockCallback() {
			@Override
			public void unlocked() {
				if( !userModel.isValid() )
					switchToSettings();
				else if( !settingsModel.isValid() ) {
					UserInfo userInfo = userModel.getUserInfo();
					if( userInfo != null && userInfo.isAdministrator() )
						switchToAdmin();
					else
						switchToWelcome();
				}
			}			
		});
		settingsModel.loadSiteInfo( loadSiteAndUserLock );
		userModel.loadCurrentUserInfo( loadSiteAndUserLock );
		
		addDefaultLinksToModel();		
	}

	
	/**
	 * Fill the model with default links
	 */
	private void addDefaultLinksToModel() {
		
		userModel.addDefaultLink( new AutoLink("Administration",
				new TokenFactory() {
				@Override
				public String createToken() {
					if( !Capabilities.canAdministrateSite( userModel.getUserInfo() ) ) return null;
					return getAdminToken();
				}}));

		userModel.addDefaultLink( new AutoLink("Welcome page",
				new TokenFactory() {
				@Override
				public String createToken() {
					return getWelcomeToken();
				}}));		
		
		userModel.addDefaultLink( new AutoLink("Search puzzles",
				new TokenFactory() {
				@Override
				public String createToken() {
					return getSearchToken();
				}}));

		userModel.addDefaultLink( new AutoLink("New Puzzle",
				new TokenFactory() {
					@Override
					public String createToken() {
						if( !Capabilities.canCreatePuzzle( userModel.getUserInfo() ) ) return null;
						return getNewPuzzleToken();
					}}));

		userModel.addDefaultLink( new AutoLink("New Tutorial",
				new TokenFactory() {
					@Override
					public String createToken() {
						if( !Capabilities.canCreateTutorial( userModel.getUserInfo() ) ) return null;
						return getNewTutorialToken();
					}}));

		for (PuzzleListInfoSimple.ListType listType : PuzzleListInfoSimple.ListType.values()) {
			final PuzzleListInfoSimple puzzleListInfo = PuzzleListInfoSimple.getInstance( listType );
			userModel.addDefaultLink( new AutoLink(puzzleListInfo.getName(),
					new TokenFactory() {
						@Override
						public String createToken() {
							if( !puzzleListInfo.canUserGetList( userModel.getUserInfo() ) )
								return null;
							return getPuzzleListToken(puzzleListInfo);
						}}));
		}
				
		userModel.notifyObservers();		
	}

	@Override
	protected void pageIdUpdated() {
		view.updateTokens();
	}
	
	@Override
	public void gotoDefaultState() {
		switchToWelcome();
	}	
	
	@Override
	public String getDefaultStateToken() {
		return getWelcomeToken();
	}
	
	@Override
	protected HistoryState forcedState() {
		if( !loadSiteAndUserLock.locked() ) {
			if( !userModel.isValid() )
				return settingsState;
			else if( !settingsModel.isValid() ) {
				UserInfo userInfo = userModel.getUserInfo();
				if( userInfo != null && userInfo.isAdministrator() )
					return adminState;
				else
					return welcomeState;
			}
		}
		return null;
	}

	
	/**
	 * Switch to the welcome page
	 */
	public void switchToWelcome() {
		if( !leaveCurrentState() ) return;
		enterState( welcomeState );
	}
	/**
	 * Creates an history token for switchToWelcome
	 * 
	 * @return The history token
	 */
	public String getWelcomeToken() {
		return buildToken( WelcomeState.STATE_TOKEN, null );
	}
	
	/**
	 * Switches to the state where the puzzle list is displayed
	 * 
	 * @param listInfo Information about the list to get 
	 */
	public void switchToPuzzleList(PuzzleListInfo listInfo) {
		if( !leaveCurrentState() ) return;
		puzzleListState.setParams(listInfo);
		enterState( puzzleListState );
	}

	/**
	 * Creates an history token for switchToPuzzleList
	 * 
	 * @param listInfo Information about the list to get 
	 * @return The history token
	 */
	public String getPuzzleListToken(PuzzleListInfo listInfo) {
		return buildToken( PuzzleListState.STATE_TOKEN, 
				PuzzleListState.getParams(listInfo) );
	}

	/**
	 * Switch to the search of a puzzle
	 */
	public void switchToSearchPuzzle() {
		if( !leaveCurrentState() ) return;
		enterState( searchState );
	}
	/**
	 * Creates an history token for searching a puzzle
	 * 
	 * @return The history token
	 */
	public String getSearchToken() {
		return buildToken( SearchState.STATE_TOKEN, null );
	}
	
	/**
	 * Switch to the creation of a new puzzle
	 */
	public void switchToNewPuzzle() {
		if( !leaveCurrentState() ) return;
		newPuzzleState.setParams(false);
		enterState( newPuzzleState );
	}
	/**
	 * Creates an history token for switchToNewPuzzle
	 * 
	 * @return The history token
	 */
	public String getNewPuzzleToken() {
		return buildToken( NewPuzzleState.STATE_TOKEN, NewPuzzleState.getParams(false) );
	}
	
	/**
	 * Switch to the edition of a new puzzle
	 * 
	 * @param newPuzzleInfo All the information for this new puzzle
	 */
	public void switchToEditNewPuzzle( NewPuzzleInfo newPuzzleInfo ) {

		assert( currentState == newPuzzleState );

		PuzzleModel model = createNewModel( newPuzzleInfo );
		if( model == null )
			return;
		HistoryImpl history = new HistoryImpl();
		if( newPuzzleInfo.isTutorial() ) {
			history.setInsert( true );
			editPuzzleState.setParams(model, history, EditPuzzleState.Type.EDIT_TUTORIAL );
		} else
			editPuzzleState.setParams(model, history, EditPuzzleState.Type.EDIT_PUZZLE );

		if( !leaveCurrentState() ) return;
		enterState( editPuzzleState );
	}

	/**
	 * Switch to the creation of a new puzzle
	 */
	public void switchToNewTutorial() {
		if( !leaveCurrentState() ) return;
		newPuzzleState.setParams(true);
		enterState( newPuzzleState );
	}
	/**
	 * Creates an history token for switchToNewPuzzle
	 * 
	 * @return The history token
	 */
	public String getNewTutorialToken() {
		return buildToken( NewPuzzleState.STATE_TOKEN, NewPuzzleState.getParams(true) );
	}
	
	/**
	 * Switch to the state for loading a new puzzle
	 * 
	 * @param infoKey The Key of the PuzzleInfo or HistoryInfo to load
	 * @param type The edition/playing type for the puzzle
	 */
	public void switchToLoad(String infoKey, Type type) {
		if( !leaveCurrentState() ) return;
		loadPuzzleState.setParams(infoKey, type);
		enterState( loadPuzzleState );
	}


	/**
	 * Creates an history token for switchToLoad
	 * 
	 * @param infoKey The key of the PuzzleInfo or HistoryInfo to load with this token
	 * @param isHistory true to load a history, false to load a puzzle
	 * @param type The edition/playing type for the puzzle
	 * @return The history token
	 */
	public String getLoadPuzzleToken(String infoKey, Type type) {
		return buildToken( LoadPuzzleState.STATE_TOKEN,
						LoadPuzzleState.getParams(infoKey, type) );
	}	

	@Override
	public String getEditPuzzleToken(String puzzleInfoKey) {
		return getLoadPuzzleToken(puzzleInfoKey, Type.EDIT_PUZZLE);
	}

	@Override
	public String getPlayPuzzleToken(String puzzleInfoKey) {
		return getLoadPuzzleToken(puzzleInfoKey, Type.PLAY_PUZZLE);
	}


	@Override
	public String getContinueHistoryToken(String historyInfoKey) {
		return getLoadPuzzleToken(historyInfoKey, Type.CONTINUE_PUZZLE);
	}


	@Override
	public String getViewHistoryToken(String historyInfoKey) {
		return getLoadPuzzleToken(historyInfoKey, Type.VIEW_SOLUTION);
	}	

	@Override
	public String getEditTutorialToken(String historyInfoKey) {
		return getLoadPuzzleToken(historyInfoKey, Type.EDIT_TUTORIAL);
	}

	@Override
	public String getViewTutorialToken(String historyInfoKey) {
		return getLoadPuzzleToken(historyInfoKey, Type.VIEW_TUTORIAL);
	}

	/**
	 * Switch to the state to display puzzle information 
	 * 
	 * @param puzzleKey The key of the puzzle for which to display information
	 */
	public void switchToPuzzleInfo(String puzzleKey) {
		if( !leaveCurrentState() ) return;
		puzzleInfoState.setParams(puzzleKey,null,false);
		enterState( puzzleInfoState );
	}

	@Override
	public String getPuzzleInfoToken(String puzzleInfoKey) {
		return buildToken( PuzzleInfoState.STATE_TOKEN, PuzzleInfoState.getParams(puzzleInfoKey,null,false) );
	}

	/**
	 * Switch to the state to display puzzle information 
	 * 
	 * @param puzzleKey The key of the puzzle for which to display information
	 */
	public void switchToTutorialInfo(String puzzleKey) {
		if( !leaveCurrentState() ) return;
		puzzleInfoState.setParams(puzzleKey,null,true);
		enterState( puzzleInfoState );
	}

	@Override
	public String getTutorialInfoToken(String puzzleInfoKey) {
		return buildToken( PuzzleInfoState.STATE_TOKEN, PuzzleInfoState.getParams(puzzleInfoKey,null,true) );
	}
	
	/**
	 * Switch to the registration of a new user 
	 */
	public void switchToSettings() {
		if( !leaveCurrentState() ) return;
		settingsState.setParams( (String)null );
		enterState( settingsState );
	}

	/**
	 * Creates an history token for switchToSettings()
	 * 
	 * @return The history token
	 */
	public String getSettingsToken() {
		return buildToken( SettingsState.STATE_TOKEN, 
				SettingsState.getParams(null) );
	}

	/**
	 * Switches to the state for administration
	 */
	public void switchToAdmin() {
		if( !leaveCurrentState() ) return;
		adminState.setParams( (String)null );
		enterState( adminState );
	}

	/**
	 * Creates an history token for switchToAdmin
	 * 
	 * @return The history token
	 */
	public String getAdminToken() {
		return buildToken( AdminState.STATE_TOKEN, 
				AdminState.getParams(null) );
	}
	
	/**
	 * Switch to the edition of a loaded puzzle
	 * 
	 * @param puzzleInfo The detailed information regarding the newly loaded puzzle
	 * @param historyInfo The detailed information regarding the newly loaded history (null if none)
	 * @param type Type of edition/play mode for this puzzle
	 */
	public void switchToEditLoadedPuzzle(PuzzleInfo puzzleInfo, HistoryInfo historyInfo, Type type) {

		try {
			if( !leaveCurrentState() ) return;

			PuzzleModel model = puzzleInfo.getPuzzleModel();
			if( model == null && (type == Type.EDIT_PUZZLE || type == Type.EDIT_TUTORIAL) ) {
				// Create a model for the puzzle
				model = createModel( puzzleInfo.getGenre() );
				model.attach( puzzleInfo );
			}
			if( model == null )
				throw new Exception( "No model for puzzle edition or playing" );
			if( (type == Type.CONTINUE_PUZZLE || type == Type.VIEW_SOLUTION || type == Type.VIEW_TUTORIAL ) &&
					historyInfo == null )
				throw new Exception( "No history, can't continue or view solution, or view tutorial" );
			if( type != Type.EDIT_PUZZLE && type != Type.EDIT_TUTORIAL )
				model.setCurrentStateAsSolution();
			History history;
			
			if( historyInfo == null )
				history = new HistoryImpl();
			else
				history = historyInfo.getHistoryModel();
			
			if( type != Type.VIEW_SOLUTION && type != Type.VIEW_TUTORIAL )
				history.applyMovesUpToCurrentTime(model);
			else
				history.forceCurrentMoveToZero();

			if( type == Type.VIEW_TUTORIAL )
				history.moveTimeZeroIntoModel(model);
			
			if( type == Type.EDIT_TUTORIAL )
				history.setInsert( true );
			else
				history.setInsert( false );
				
			history.notifyObservers();
			model.notifyObservers();
			
			editPuzzleState.setParams(model, history, type );
			
			enterState( editPuzzleState );
		} catch (Exception exception) {
			exception.printStackTrace();
			backToPreviousState();
		}			
	}

	/**
	 * Creates a new model given a genre
	 * 
	 * @param genre The genre for which to create a model
	 * @return The newly created puzzle model or null if failed
	 */
	private PuzzleModel createModel(String genre) {
		PuzzleFactory factory = GenreFactory.getInstance().getFactory( genre );
		if( factory == null ) return null;
		PuzzleModel model;
		try {
			model = factory.newModel();
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
		return model;
	}


	/**
	 * Creates a new puzzle model and atta
	 * 
	 * @param newPuzzleInfo The information for this new puzzle
	 * @return The newly created model, null if creation failed
	 */
	private PuzzleModel createNewModel(NewPuzzleInfo newPuzzleInfo) {

		PuzzleModel model = createModel (newPuzzleInfo.getGenre());
		if( model != null )
			model.create(newPuzzleInfo);
		return model;
	}

	private class MyMainTokenFactory implements MainTokenFactory {

		@Override
		public String createSettingsToken() {
			return getSettingsToken();
		}

	}


	
}
